# -*- coding: utf-8 -*-

"""
@author: Laowang
@contact: QQ:1125564921
@Created on: 2022/6/21
@Remark: 
"""
import operator
from typing import Any
from bisect import bisect
import json
import requests
import logging
from collections import namedtuple
from typing import Dict
import requests
import json
from functools import wraps
from typing import Dict, List
from copy import deepcopy

from functools import partial

from framework_strategy.gopy.base.type import ResponseParamterType
from framework_strategy.gopy.gateways.adapter import request as requestSession


RequestParamterObj = namedtuple(
    "RequestParamterObj", [
        "method",
        "uri",
        "data",
    ])


ResponseEventObj = namedtuple(
    "ResponseEventObj", [
        "status",
        "data",
        "response",
        "event"
    ])

ResponseKlineObj = namedtuple(
    "ResponseKlineObj", [
        "OpenTime",
        "OpenPrice",
        "High",
        "Low",
        "Close",
        "Volume"
    ])


ResponseTickerObj = namedtuple(
    "ResponseTickerObj", [
        "bidPrice",
        "bidQty",
        "askPrice",
        "askQty",
        "lastPrice",
        "symbol",
    ])

ResponseOrderBookObj = namedtuple(
    "ResponseOrderBookObj", [
        "asks",
        "bids"
    ])


ResponseTradesObj = namedtuple(
    "ResponseTradesObj", [
        "orderId",
        "pirce",
        "quantity",
        "side",
        "time",
    ])

ResponseOrderObj = namedtuple(
    "ResponseOrderObj", [
        "orderId",
        "market",
        "side",
        "price",
        "number",
    ])

ResponseOrdersObj = namedtuple(
    "ResponseOrdersObj", [
        "quantity",
        "price",
        "orderId",
        "side",
    ])

ResponseCancelOrderObj = namedtuple(
    "ResponseCancelOrderObj", [
        "orderId",
        "status",
    ])

ResponseAccountOrderObj = namedtuple(
    "ResponseAccountOrderObj", [
        "quantity",
        "price",
        "orderId",
        "status",
        "side",
        "type",
        "symbol",
    ])

ResponseMineOrdersObj = namedtuple(
    "ResponseMineOrdersObj", [
        "orderId",
        "time",
        "price",
        "quantity",
        "amount",
        "side",
        "type",
        "fee",
        "takerOrMaker",
    ])


"""
balances : [   
{
    "asset": "BTC",
    "free": "4723846.89208129",
    "locked": "0.00000000"
},
]
"""
ResponseBalanceObj = namedtuple(
    "ResponseBalanceObj", [
        "asset",
        "free",
        "locked",
    ])


# --------------------------------------------------------------------------------
class RequestPrepare:
    """ """

    def __init__(self) -> None:
        self._session: requests = requestSession

    def do_get(self, **kwargs):
        kwargs["params"] = kwargs.pop("json", {}) if kwargs.get("json") else kwargs.get("params")
        return self._session.get(**kwargs)

    def do_post(self, **kwargs):
        kwargs["data"] = kwargs.pop("data", {}) if kwargs.get("data") else kwargs["params"]
        return self._session.post(**kwargs)

    def do_delete(self, **kwargs):
        # queryString
        kwargs["params"] = kwargs.pop("json", {}) if kwargs.get("json") else kwargs.get("params")
        return self._session.delete(**kwargs)

    def dispatch(self, method: str):

        __func__ = "do_" + method.lower()
        if not hasattr(self, __func__):
            raise AttributeError("No found {} attribute".format(__func__))
        return __func__

    def do_request(self, method, **kwargs):
        attr = self.dispatch(method)
        cls = operator.methodcaller(attr, **kwargs)
        response = cls(self)
        return response


request_prepare: RequestPrepare = RequestPrepare()

# Template
def request(
        method: str,
        url: str,
        parmas: Dict,
        proxy: Dict,
        *,
        json=None,
        exchange=None,
        _api_method=None,
        _api_market=None,
        hooks=None,
        headers=None,
        times: int = 1,
        pop_header_body =None,
        timeout: float = 3) -> ResponseParamterType:
    """
    API request methods:  
    Pay attention to the attribute of the binding. 
    If the attribute does not exist, it may raise the corresponding exception 
    """
    i = 0
    response = ""
    headers = headers or {}
    if parmas:
        _parmas = deepcopy(parmas)
        _parmas["exchange"] = exchange or ""
        _parmas["_api_method"] = _api_method or ""
        _parmas["_api_market"] = _api_market or ""
        if not pop_header_body:
            headers["body"] = repr(_parmas)
    while i < times:
        try:
            response = request_prepare.do_request(
                method,
                url=url,
                params=parmas,
                timeout=timeout,
                proxies=proxy,
                hooks=hooks,
                json=json,
                headers=headers
            )
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            logging.info('Request timeout, retry operation::[{e}, content:{t}]........'.format(e=e,t=e.response.text))  # NOQA
        else:
            if response.status_code == 200 and response:
                # self.timer_active = False
                res = response.json()
                return True, res, response
        finally:
            i += 1
            # print("Please wait a moment...retry now... times is {}".format(i))  # NOQA
    else:
        logging.error(
            'error Request timeout, retry operation failed::{}::{}'.format(url, parmas))  # NOQA

    return False, parmas, response


# To json
def response_to_format(data):
    return json.dumps(data, ensure_ascii=False)


class _handle:
    """ request handle hook """

    def __init__(self, func=None, *, key=None, response_handle=None) -> None:
        """ """

        self._func = func
        self._handle = key
        self._response_handle = response_handle

    def __call__(self, this, event) -> Any:
        """
        this : gatewaybase
        event: Event
        """
        def prepare__(this=this, event=event): return self._prepare(
            this, event)
        return this.main_engine.prepare(prepare__, this, event)

    def _prepare(self, this, event):
        """ """
        res = parameter = self._func(this, event)  # type -> RequestParamterObj
        if self._handle:
            res = self._handle(this, event, parameter)

        # response
        if self._response_handle is not None:
            res = res._replace(data=self._response_handle(res.data, parameter))
        return res, parameter


# Need auth
# You can do request statistics here,
# as well as response results record
# or do stream limiting here etc
def auth_require(func=None, *, key=None, response_handle=None):
    """
    ::Parameter
    ---
        * This here refers to the class instance object being called  
        * Event refers to the event content that drives the event 
    """
    if func is None:
        return partial(auth_require, key=key, response_handle=response_handle)

    @wraps(func)
    def inner(this, event) -> RequestParamterObj:
        """ """
        Handle = _handle(func=func, key=key, response_handle=response_handle)
        res, parameter = Handle(this, event)

        # isasynchronous
        # Note that if you use the same object,
        # queue blocking may occur when doing multiple events
        if not parameter.data.event_data.isasynchronous:
            parameter.data.event_data.put(res)
        return res
    return inner


# Public
# You can do request statistics here,
# as well as response results record
# or do stream limiting here etc
def public_request(func=None, *, key=None, response_handle=None):
    """
    ::Parameter
    ---
        * This here refers to the class instance object being called  
        * Event refers to the event content that drives the event 
    """
    if func is None:
        return partial(public_request, key=key, response_handle=response_handle)

    @wraps(func)
    def inner(this, event) -> RequestParamterObj:
        """ """
        Handle = _handle(func=func, key=key, response_handle=response_handle)
        res, parameter = Handle(this, event)
        # isasynchronous
        # Note that if you use the same object,
        # queue blocking may occur when doing multiple events
        if not parameter.data.event_data.isasynchronous:
            parameter.data.event_data.put(res)
        return res
    return inner
